package com.gzc.just.play.last.war.robotclient;

import com.gzc.just.play.last.war.common.handler.VirtualThreadServerFramework;
import com.gzc.just.play.last.war.common.net.NetUtils;
import com.gzc.just.play.last.war.common.scanner.MessageHandlerScanner;
import com.gzc.just.play.last.war.protobuf.bs.bstogc.BSToGC;
import com.gzc.just.play.last.war.protobuf.gc.gctobs.GCToBS;
import com.gzc.just.play.last.war.protobuf.gc.gctocs.GCToCS;
import com.gzc.just.play.last.war.protobuf.gc.gctols.GCToLS;
import com.gzc.just.play.last.war.robotclient.statemachine.RobotState;
import com.gzc.just.play.last.war.robotclient.statemachine.RobotStateMachine;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.AttributeKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.EnumMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 基于Netty的机器人客户端
 * 使用统一的VirtualThreadServerFramework虚拟线程框架处理消息
 */
public class NettyRobotClient {
    private static final Logger log = LoggerFactory.getLogger(NettyRobotClient.class);
    public static final AttributeKey<NettyRobotClient> CLIENT_KEY = AttributeKey.valueOf("NettyRobotClient");

    // 机器人状态
    private final RobotClientConfig config;
    private final long robotId;
    private final AtomicBoolean connected = new AtomicBoolean(false);
    private volatile long connectionTime = 0;

    // 网络组件
    private final EventLoopGroup workerGroup;
    private final Bootstrap bootstrap;
    private volatile Channel channel;
    private volatile boolean connecting = false;
    private volatile boolean running = false;

    // 虚拟线程框架
    private final VirtualThreadServerFramework virtualThreadFramework;
    private volatile long sessionId = -1;

    // 状态机
    private final RobotStateMachine stateMachine;
    private final EnumMap<RobotState, Runnable> stateActions;
    private volatile String gateToken = "token_placeholder";

    // 统计信息
    private final AtomicLong messageCount = new AtomicLong(0);
    private final AtomicLong sendCount = new AtomicLong(0);
    private final AtomicLong recvCount = new AtomicLong(0);
    private volatile long lastHeartbeatTime = 0;

    private final MessageHandlerScanner messageHandlerScanner;

    public NettyRobotClient(RobotClientConfig config, MessageHandlerScanner messageHandlerScanner) {
        this.config = config;
        this.robotId = config.getRobotId();
        this.messageHandlerScanner = messageHandlerScanner;
        this.workerGroup = new NioEventLoopGroup(1);
        this.bootstrap = new Bootstrap();

        // 创建虚拟线程框架
        this.virtualThreadFramework = new VirtualThreadServerFramework(
            VirtualThreadServerFramework.ServerType.ROBOT_CLIENT);

        this.stateMachine = new RobotStateMachine();
        this.stateActions = initStateActions();

        initMessageHandlers();
        initBootstrap();
    }
    
    private EnumMap<RobotState, Runnable> initStateActions() {
        EnumMap<RobotState, Runnable> actions = new EnumMap<>(RobotState.class);
        actions.put(RobotState.LOGIN_SERVER, this::sendLoginRequest);
        actions.put(RobotState.BALANCE_SERVER, this::sendBalanceLoginRequest);
        actions.put(RobotState.GATE_SERVER, this::sendGateLoginRequest);
        return actions;
    }
    
    /**
     * 初始化消息处理器
     */
    private void initMessageHandlers() {
        if (messageHandlerScanner != null) {
            messageHandlerScanner.registerHandlersForServerType(
                VirtualThreadServerFramework.ServerType.ROBOT_CLIENT,
                this.virtualThreadFramework
            );
        } else {
            log.warn("MessageHandlerScanner is null, no handlers registered for robot {}", robotId);
        }
    }

    // Getters
    public long getRobotId() { return robotId; }
    public RobotClientConfig getConfig() { return config; }
    public boolean isConnected() { return connected.get(); }
    public void setConnected(boolean isConnected) {
        this.connected.set(isConnected);
        if (isConnected) {
            this.connectionTime = System.currentTimeMillis();
        }
    }
    public long getConnectionTime() { return connectionTime; }
    public void setConnectionTime(long connectionTime) { this.connectionTime = connectionTime; }

    private void initBootstrap() {
        bootstrap.group(workerGroup)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast("idleStateHandler", new IdleStateHandler(0, 0, 30, TimeUnit.SECONDS));
                        NetUtils.addNettyCodec(pipeline);
                        pipeline.addLast("virtualThreadHandler", new ChannelInboundHandlerAdapter() {
                            @Override
                            public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                ctx.channel().attr(CLIENT_KEY).set(NettyRobotClient.this);
                                sessionId = virtualThreadFramework.startSession(ctx);
                                log.info("Robot {} connected with session ID: {}", robotId, sessionId);
                                Runnable action = stateActions.get(stateMachine.getCurrentState());
                                if (action != null) {
                                    action.run();
                                }
                            }

                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                if (msg instanceof io.netty.buffer.ByteBuf byteBuf) {
                                    byte[] messageBytes = new byte[byteBuf.readableBytes()];
                                    byteBuf.readBytes(messageBytes);
                                    if (messageBytes.length < 4) {
                                        return;
                                    }
                                    int messageId = ((messageBytes[0] & 0xFF) << 24) |
                                                   ((messageBytes[1] & 0xFF) << 16) |
                                                   ((messageBytes[2] & 0xFF) << 8) |
                                                   (messageBytes[3] & 0xFF);
                                    byte[] messageContent = new byte[messageBytes.length - 4];
                                    System.arraycopy(messageBytes, 4, messageContent, 0, messageContent.length);
                                    messageCount.incrementAndGet();
                                    virtualThreadFramework.submitMessage(sessionId, messageId, messageContent);
                                }
                            }

                            @Override
                            public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                                if (sessionId != -1) {
                                    virtualThreadFramework.stopSession(sessionId);
                                    log.info("Robot {} disconnected with session ID: {}", robotId, sessionId);
                                    sessionId = -1;
                                }
                                setConnected(false);
                            }

                            @Override
                            public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                                log.error("Robot {} connection error", robotId, cause);
                                setConnected(false);
                                ctx.close();
                            }
                        });
                    }
                });
    }

    private void sendLoginRequest() {
        try {
            GCToLS.AskLogin askLogin = GCToLS.AskLogin.newBuilder()
                .setMsgid(GCToLS.MsgID.eMsgToLSFromGC_AskLogin)
                .setPlatform(config.getPlatformId())
                .setUin(config.getUsername())
                .setSessionid("test_token_" + System.currentTimeMillis())
                .build();
            sendMessage(40961, askLogin);
            log.info("Robot {} sent login request to LS for user {}", robotId, config.getUsername());
        } catch (Exception e) {
            log.error("Robot {} failed to send LS login request", robotId, e);
        }
    }

    private void sendBalanceLoginRequest() {
        try {
            GCToBS.OneClinetLogin login = GCToBS.OneClinetLogin.newBuilder()
                .setMsgnum(GCToBS.MsgNum.eMsgToBSFromGC_OneClinetLogin)
                .setPlat(config.getPlatformId())
                .setUin(config.getUsername())
                .setSessionid("test_token_" + System.currentTimeMillis())
                .setLoginSuccess(1)
                .setNsid(0)
                .build();
            sendMessage(32770, login);
            log.info("Robot {} sent login request to BS for user {}", robotId, config.getUsername());
        } catch (Exception e) {
            log.error("Robot {} failed to send BS login request", robotId, e);
        }
    }

    private void sendGateLoginRequest() {
        try {
            GCToCS.Login login = GCToCS.Login.newBuilder()
                .setMsgnum(GCToCS.MsgNum.eMsgToGSToCSFromGC_AskLogin)
                .setSdk(0)
                .setPlatform(config.getPlatformId())
                .setName(config.getUsername())
                .setPasswd(gateToken)
                .setIpaddress("127.0.0.1")
                .setEquimentid("device_id")
                .build();
            sendMessage(8193, login);
            log.info("Robot {} sent login request to Gate for user {}", robotId, config.getUsername());
        } catch (Exception e) {
            log.error("Robot {} failed to send Gate login request", robotId, e);
        }
    }

    public CompletableFuture<Boolean> connectToLoginServer() {
        return connectToLoginServer(config.getLoginServerHost(), config.getLoginServerPort());
    }

    public CompletableFuture<Boolean> connectToLoginServer(String host, int port) {
        stateMachine.switchTo(RobotState.LOGIN_SERVER);
        return connect(host, port);
    }

    public CompletableFuture<Boolean> connectToBalanceServer(String host, int port) {
        stateMachine.switchTo(RobotState.BALANCE_SERVER);
        return connect(host, port);
    }

    public CompletableFuture<Boolean> connectToGateServer(String host, int port, String token) {
        if (token != null && !token.isEmpty()) {
            this.gateToken = token;
        }
        stateMachine.switchTo(RobotState.GATE_SERVER);
        return connect(host, port);
    }

    public CompletableFuture<Boolean> connect(String host, int port) {
        if (connected.get() || connecting) {
            disconnect();
        }
        connecting = true;
        CompletableFuture<Boolean> future = new CompletableFuture<>();
        bootstrap.connect(host, port).addListener((ChannelFuture channelFuture) -> {
            connecting = false;
            if (channelFuture.isSuccess()) {
                channel = channelFuture.channel();
                connected.set(true);
                setConnectionTime(System.currentTimeMillis());
                log.info("Robot {} connected to server {}:{}", robotId, host, port);
                future.complete(true);
            } else {
                log.error("Robot {} failed to connect to {}:{}: {}", robotId, host, port, channelFuture.cause().getMessage());
                future.complete(false);
            }
        });
        return future;
    }

    public CompletableFuture<Boolean> connect() {
        return connectToLoginServer();
    }

    public void disconnect() {
        if (connected.get()) {
            connected.set(false);
            running = false;
            if (sessionId != -1) {
                virtualThreadFramework.stopSession(sessionId);
                sessionId = -1;
            }
            if (channel != null) {
                channel.close();
            }
            log.info("Robot {} disconnected", robotId);
        }
    }

    public void shutdown() {
        disconnect();
        workerGroup.shutdownGracefully();
    }

    public CompletableFuture<Void> sendMessage(int messageId, Object data) {
        if (!connected.get() || channel == null) {
            return CompletableFuture.failedFuture(new IllegalStateException("Not connected"));
        }
        CompletableFuture<Void> future = new CompletableFuture<>();
        try {
            NetUtils.sendMsg(channel, messageId, data);
            sendCount.incrementAndGet();
            future.complete(null);
        } catch (Exception e) {
            log.error("Robot {} failed to send message: {}", robotId, e.getMessage());
            future.completeExceptionally(e);
        }
        return future;
    }

    public void startHeartbeat() {
        running = true;
        Thread.startVirtualThread(() -> {
            Thread.currentThread().setName("Robot-" + robotId + "-Heartbeat");
            while (running && connected.get()) {
                try {
                    Thread.sleep(5000);
                    sendMessage(1002, new byte[0]).get();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                } catch (Exception e) {
                    log.error("Robot {} heartbeat error", robotId, e);
                }
            }
        });
    }

    public void stopHeartbeat() {
        running = false;
    }

    public void onHeartbeatReceived() {
        lastHeartbeatTime = System.currentTimeMillis();
        recvCount.incrementAndGet();
    }

    public long getMessageCount() { return messageCount.get(); }
    public long getSendCount() { return sendCount.get(); }
    public long getRecvCount() { return recvCount.get(); }
    public long getLastHeartbeatTime() { return lastHeartbeatTime; }
}
